//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//

#include "CFaceControl.h"
#include "CUserInterface.h"
#include "CLevelEditor.h"
#include "DoUndoCommands.h"
#include "CEventReceiver.h"
#include "ISceneCollisionManager.h"
#include "ISceneManager.h"
#include "ISceneNode.h"
#include "CModifyCommand.h"
#include "ICameraSceneNode.h"
#include "SViewFrustum.h"
#include "IrrlichtDevice.h"
//#include <cassert>
//#include <string>
//#include <vector>

extern CGlobals* g;
extern CUserInterface* ui;
extern CLevelEditor* editor;

//void drawThick2DLine (position2di p1, position2di p2, int thickness, SColor color);
//void drawThick2DLine (vector2df p1, vector2df p2, int thickness, SColor color);
bool project3DTo2D (vector3df pt, vector2df* result, ICameraSceneNode* camera = 0);
void drawThick2DArrow (vector2df p1, vector2df p2, int thickness, SColor color);
ICameraSceneNode* cloneActiveCamera ();
float pointToSegmentDistance (vector2df pt, vector2df s1, vector2df s2);
void getClosestPoints (vector3df o1, vector3df d1, vector3df o2, vector3df d2, vector3df* p1, vector3df* p2);
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
CFaceControl::CFaceControl (ISceneNode* node, vector3df axis , vector3df size , SColor normalColor, SColor highlightColor)
{
	m_type = E_FACE;
	m_node = node;
	m_axis = axis;
	
	m_normalColor = normalColor;
	m_highlightColor = highlightColor;
	m_activated = false;
	m_highlighted = false;
	m_camera = 0;
	
	updateControl( size);
	
	m_nodeStart = vector3df();
/*	for (int i = 0; i < m_nodes.size(); ++i)
	{
		m_centroid += m_nodes[i]->getAbsolutePosition();
	}
	m_centroid /= m_nodes.size();
*/
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
CFaceControl::~CFaceControl ()
{
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
void CFaceControl::updateControl( vector3df size )
{
	m_centroid = m_node->getAbsolutePosition();
	m_size = size;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
bool CFaceControl::OnEvent (const SEvent& event)
{
	switch (event.EventType)
	{
		case EET_MOUSE_INPUT_EVENT:
			return onMouseEvent(event.MouseInput);
	}
	return false;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
bool CFaceControl::isPointOverControl (position2di pt) const
{
	array<vector2df> coords;
	getScreenCoordinates(&coords);

	if (coords.size() == 2)
	{
		return pointToSegmentDistance(vector2df(f32(pt.X), f32(pt.Y)), coords[0], coords[1]) <= 4.0f;
	}

	return false;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
bool CFaceControl::onMouseEvent (const SEvent::SMouseInput& event)
{
	position2di mouse = g->device->getCursorControl()->getPosition();//(event.X, event.Y);

	if (editor->isPointOverGUI(mouse))
		return false;

	bool mouseOverControl = isPointOverControl(mouse);

	m_highlighted = mouseOverControl && !g->eventReceiver->IsKeyDown(KEY_CONTROL);
	
	int result = ui->getEditAction();

	switch (event.Event)
	{
	case EMIE_LMOUSE_PRESSED_DOWN:
		if (mouseOverControl && !g->eventReceiver->IsKeyDown(KEY_CONTROL))
		{
			array<vector2df> coords;
			f32 sign = getScreenCoordinates(&coords);
			mouse.X = (s32)coords[0].X;
			mouse.Y = (s32)coords[0].Y;
			g->device->getCursorControl()->setPosition(mouse);

			m_activated = true;
			g->eventReceiver->GrabAllEvents(this);

			switch ( result )
			{
				case UI_EDIT_ACTION_MODIFY:
					m_nodeStart = m_nodeCurent = (m_node->getPosition() + sign * m_axis * m_size / 2);
					m_nodeStartPos = m_nodeCurentPos  = (m_node->getAbsolutePosition() + sign * m_axis * m_size / 2);
				break;
			}	

			m_mouseStart = mouse;
			m_camera = cloneActiveCamera();
			return true;
		}
		break;

	case EMIE_LMOUSE_LEFT_UP:
		if (m_activated)
		{
			array<vector2df> coords;
			f32 sign = getScreenCoordinates(&coords);

			m_activated = false;
			g->eventReceiver->ReleaseGrab(this);
			m_camera->remove();
			m_camera = 0;
			
			switch ( result )
			{
				case UI_EDIT_ACTION_MODIFY:
				{
					vector3df diff = (m_nodeCurent - m_nodeStart) * sign;

					vector3df size = m_size + diff;

					matrix4 matAbsTransform = m_node->getAbsoluteTransformation();
					matAbsTransform.setTranslation(m_node->getAbsolutePosition() + sign * (diff / 2));

					matrix4 matParentAbsTransform = m_node->getParent()->getAbsoluteTransformation();
					matParentAbsTransform.makeInverse();

					m_node->setRelativeTransformation(matParentAbsTransform * matAbsTransform);
					//force update
					m_node->setPosition(m_node->getPosition());					
										
					editor->AddCommand(new CModifyCommand(m_node->getID(), m_size , size));
						
					//update atribute for the scene object
					//send event to update other controls 
					editor->updateTrigerControls( size );
					ui->updateProperties();
				}
				break;
			}
			return true;
		}
		break;

	case EMIE_MOUSE_MOVED:
		if (m_activated)
		{
			ISceneCollisionManager* colmgr = g->sceneManager->getSceneCollisionManager();

			vector3df mouseStart, moveTo;
			line3df rayStart = colmgr->getRayFromScreenCoordinates(m_mouseStart, m_camera);
			getClosestPoints(m_nodeStartPos, m_axis, rayStart.start, rayStart.getVector(), &mouseStart, 0);

			line3df rayMoveTo = colmgr->getRayFromScreenCoordinates(mouse, m_camera);
			getClosestPoints(m_nodeStartPos, m_axis, rayMoveTo.start, rayMoveTo.getVector(), &moveTo, 0);

			switch ( result )
			{				
				case UI_EDIT_ACTION_MODIFY:
				{
					vector3df delta = moveTo - mouseStart;
					//matrix4 matAbsTransform = m_node->getParent()->getAbsoluteTransformation();
					//matAbsTransform.rotateVect(delta);

					m_nodeCurent = m_nodeStart + delta;
					m_nodeCurentPos = m_nodeStartPos + delta;
				}
				break;
			}

			ui->updateProperties();
		}
		break;
	}

	return false;
}

void CFaceControl::draw ()
{
	SColor color = (m_highlighted || m_activated) ? m_highlightColor : m_normalColor;

	if (m_activated)
	{
		int result = ui->getEditAction();
		if (result == UI_EDIT_ACTION_MODIFY)
		{
			vector2df start, end;
			project3DTo2D(m_nodeStartPos, &start, m_camera);
			project3DTo2D(m_nodeCurentPos , &end, m_camera);
			drawThick2DArrow(start, end, 2, color);
		}
	}
	else
	{
		array<vector2df> coords;
		getScreenCoordinates(&coords);
		if (coords.size() == 2)
		{							
			drawThick2DArrow(coords[0], coords[1], 2, color);
		}
	}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
f32 CFaceControl::getScreenCoordinates (array<vector2df>* coords) const
{
	vector3df nodePos = m_centroid + m_axis * m_size / 2;
	ICameraSceneNode* cam = g->sceneManager->getActiveCamera();
	plane3df pl = cam->getViewFrustum()->planes[SViewFrustum::VF_NEAR_PLANE];

	float dist = -pl.getDistanceTo(nodePos);

	float sign = (plane3df(nodePos, m_axis * m_size / 2).getDistanceTo(cam->getPosition()) >= 0) ? 1.0f : -1.0f;

	nodePos = m_centroid + sign * m_axis * m_size / 2;

	float scale = 1;

	if (dist > 1.0f)
	{
		scale = dist/5.0f;
	}

	vector3df p1 = nodePos;
	vector3df p2 = p1 + sign*scale*m_axis;

	vector2df proj;
	if (project3DTo2D(p1, &proj, m_camera)) coords->push_back(proj);
	if (project3DTo2D(p2, &proj, m_camera)) coords->push_back(proj);

	return sign;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
//vector2df CFaceControl::getScreenAxis () const
//{
//	array<vector2df> coords;
//	getScreenCoordinates(&coords);
//
//	assert(coords.size() == 2);
//	
//	return coords[1] - coords[0];
//}
// returns the two points which are closest on the two lines o1+t*d1 and o2+t*d2
